package q1.project.core.jpa;

import jakarta.persistence.EntityManager;
import jakarta.validation.constraints.NotNull;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.hibernate.query.NativeQuery;
import org.hibernate.transform.Transformers;
import org.hibernate.type.*;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.support.SimpleJpaRepository;

/**
 * 支持原生 SQL 语句执行
 *
 * <p><strong>SQL 注入防护说明：</strong>
 *
 * <ul>
 *   <li><strong>⚠️ 重要：</strong>本类提供的无参数方法（如 {@code execSql(String sql, Class)}） 存在 SQL 注入风险，如果 SQL
 *       中包含用户输入，必须使用带参数的方法
 *   <li><strong>✅ 推荐：</strong>使用带 {@code Map<String, Object> parameters} 参数的方法， 通过参数绑定防止 SQL 注入
 *   <li>原生 SQL 支持两种参数绑定方式：
 *   <li>1. <strong>命名参数</strong>：使用 {@code :paramName} 格式（推荐）
 *   <li>2. <strong>位置参数</strong>：使用 {@code ?} 格式（需要按顺序传递）
 *   <li>所有参数值通过 {@code setParameter()} 方法绑定，会自动进行转义处理
 * </ul>
 *
 * <p><strong>安全使用示例：</strong>
 *
 * <pre>{@code
 * // ✅ 安全：使用命名参数
 * String sql = "SELECT * FROM sys_user WHERE username = :username AND tenant_id = :tenantId";
 * Map<String, Object> params = new HashMap<>();
 * params.put("username", userInput);  // 参数会被安全处理
 * params.put("tenantId", tenantId);
 * List<UserDTO> users = execSqlList(sql, params, UserDTO.class);
 *
 * // ❌ 危险：直接拼接 SQL（不要这样做！）
 * String sql = "SELECT * FROM sys_user WHERE username = '" + userInput + "'";
 * List<UserDTO> users = execSqlList(sql, UserDTO.class);  // SQL 注入风险！
 * }</pre>
 *
 * @author: maorui
 * @date: 2020/10/28
 */
public abstract class SqlExecuteDao<T, ID> extends SimpleJpaRepository<T, ID> {
  public static final Map<Class<?>, Class<?>> CLASS_TYPE_MAP = new HashMap<>();

  static {
    // 在Hibernate 6中，直接使用类型类
    CLASS_TYPE_MAP.put(Integer.class, Integer.class);
    CLASS_TYPE_MAP.put(Long.class, Long.class);
    CLASS_TYPE_MAP.put(Float.class, Float.class);
    CLASS_TYPE_MAP.put(Double.class, Double.class);
    CLASS_TYPE_MAP.put(BigDecimal.class, BigDecimal.class);
    CLASS_TYPE_MAP.put(String.class, String.class);
    CLASS_TYPE_MAP.put(LocalDate.class, LocalDate.class);
    CLASS_TYPE_MAP.put(LocalDateTime.class, LocalDateTime.class);
    CLASS_TYPE_MAP.put(LocalTime.class, LocalTime.class);
  }

  private EntityManager entityManager;

  public SqlExecuteDao(Class<T> clazz, EntityManager entityManager) {
    super(clazz, entityManager);
    this.entityManager = entityManager;
  }

  public <T> Page<T> execSqlWithPage(
      @NotNull String sql, @NotNull Pageable pageable, @NotNull Class<T> clazz) {
    NativeQuery nativeQuery = (NativeQuery) entityManager.createNativeQuery(sql);
    this.addScalar(nativeQuery, clazz, false);
    // 在Hibernate 6.x中，直接设置结果转换器到NativeQuery
    nativeQuery.setResultTransformer(Transformers.aliasToBean(clazz));
    nativeQuery.setFirstResult(pageable.getPageNumber() * pageable.getPageSize());
    nativeQuery.setMaxResults(pageable.getPageSize());
    return new PageImpl<>(nativeQuery.getResultList(), pageable, totalCount(sql));
  }

  public <T> T execSql(@NotNull String sql, @NotNull Class<T> clazz) {
    NativeQuery nativeQuery = (NativeQuery) entityManager.createNativeQuery(sql);
    this.addScalar(nativeQuery, clazz, false);
    // 在Hibernate 6.x中，直接设置结果转换器到NativeQuery
    nativeQuery.setResultTransformer(Transformers.aliasToBean(clazz));
    return (T) nativeQuery.uniqueResult();
  }

  public <T> List<T> execSqlList(@NotNull String sql, @NotNull Class<T> clazz) {
    return execSqlList(sql, clazz, false);
  }

  public <T> List<T> execSqlList(
      @NotNull String sql, @NotNull Class<T> clazz, boolean isIgnoreSuperclass) {
    NativeQuery nativeQuery = (NativeQuery) entityManager.createNativeQuery(sql);
    this.addScalar(nativeQuery, clazz, isIgnoreSuperclass);
    // 在Hibernate 6.x中，直接设置结果转换器到NativeQuery
    nativeQuery.setResultTransformer(Transformers.aliasToBean(clazz));
    List resultList = nativeQuery.getResultList();
    List<T> respList = new ArrayList<T>();
    respList.addAll(resultList);
    return respList;
  }

  ///////////////////////////////////// private

  private Long totalCount(@NotNull String sql) {
    sql = "select count(1) from (" + sql + ") countAlias";
    NativeQuery query = (NativeQuery) entityManager.createNativeQuery(sql);
    BigInteger bigInteger = (BigInteger) query.uniqueResult();
    return bigInteger.longValue();
  }

  /**
   * 添加 字段类型
   *
   * @param nativeQuery sql查询语句
   * @param clazz 类
   */
  private void addScalar(
      NativeQuery nativeQuery, @NotNull Class clazz, boolean isIgnoreSuperclass) {
    for (Field field : clazz.getDeclaredFields()) {
      Class<?> targetType = CLASS_TYPE_MAP.get(field.getType());
      if (targetType == null) {
        continue;
      }

      // 如果是final字段也排除
      if (Modifier.isFinal(field.getModifiers())) {
        continue;
      }

      // 在Hibernate 6中，使用类型类添加标量
      nativeQuery.addScalar(field.getName(), targetType);
    }

    if (clazz.getSuperclass() != null && !isIgnoreSuperclass) {
      addScalar(nativeQuery, clazz.getSuperclass(), false);
    }
  }
}
